﻿/****
 * Copyright (c) 2012 Nabil Abdel-Hafeez
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files probuff,
 * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
****/
using System;
using System.Collections;
using System.IO;
using System.Text;


namespace ProBuff
{
    public class Serializer
    {
        /// <summary>
        /// Stores the result of the varint encoded value before written to stream.
        /// </summary>
        private static byte[] result = new byte[10];
        /// <summary>
        /// Reads a varint and returns the decoded value and it's length as int.
        /// </summary>
        /// <param name="array">The byte array to read the varint from. If array length is below 5 the result will be 0.</param>
        /// <param name="offset">The position where the varint encoding starts.</param>
        /// <returns>An array with length 2. The decoded varint is at 0 and it's length at 1.</returns>
        public static int[] getLength32(byte[] array, long offset = 0)
        {
            int[] length = { 0, 0 };
            long i = offset;
            int b = 0;
            if (array.Length < 5) goto done;
            b = array[i++]; length[0] = (b & 0x7F); length[1]++; if ((b & 0x80) == 0x00) goto done;
            b = array[i++]; length[0] |= (b & 0x7F) << 7; length[1]++; if ((b & 0x80) == 0x00) goto done;
            b = array[i++]; length[0] |= (b & 0x7F) << 14; length[1]++; if ((b & 0x80) == 0x00) goto done;
            b = array[i++]; length[0] |= (b & 0x7F) << 21; length[1]++; if ((b & 0x80) == 0x00) goto done;
            b = array[i++]; length[0] |= (b & 0x7F) << 28; length[1]++; if ((b & 0x80) == 0x00) goto done;

        done:
            return length;
        }
        /// <summary>
        /// Reads a varint and returns the decoded value and it's length as long.
        /// </summary>
        /// <param name="array">The byte array to read the varint from. If array length is below 10 the result will be 0.</param>
        /// <param name="offset">The position where the varint encoding starts.</param>
        /// <returns>An array with length 2. The decoded varint is at 0 and it's length at 1.</returns>
        public static long[] getLength64(byte[] array, long offset = 0)
        {
            long[] length = { 0, 0 };
            long i = offset;
            long b = 0;
            if (array.Length < 10) goto done;
            b = array[i++]; length[0] = (b & 0x7F); length[1]++; if ((b & 0x80) == 0x00) goto done;
            b = array[i++]; length[0] |= (b & 0x7F) << 7; length[1]++; if ((b & 0x80) == 0x00) goto done;
            b = array[i++]; length[0] |= (b & 0x7F) << 14; length[1]++; if ((b & 0x80) == 0x00) goto done;
            b = array[i++]; length[0] |= (b & 0x7F) << 21; length[1]++; if ((b & 0x80) == 0x00) goto done;
            b = array[i++]; length[0] |= (b & 0x7F) << 28; length[1]++; if ((b & 0x80) == 0x00) goto done;
            b = array[i++]; length[0] |= (b & 0x7F) << 35; length[1]++; if ((b & 0x80) == 0x00) goto done;
            b = array[i++]; length[0] |= (b & 0x7F) << 42; length[1]++; if ((b & 0x80) == 0x00) goto done;
            b = array[i++]; length[0] |= (b & 0x7F) << 49; length[1]++; if ((b & 0x80) == 0x00) goto done;
            b = array[i++]; length[0] |= (b & 0x7F) << 56; length[1]++; if ((b & 0x80) == 0x00) goto done;
            b = array[i++]; length[0] |= (b & 0x7F) << 63; length[1]++; if ((b & 0x80) == 0x00) goto done;
        done:
            return length;
        }
        /// <summary>
        /// Reads a varint and returns the decoded value as a long.
        /// </summary>
        /// <param name="mem">The System.IO.MemoryStream to read the varint from.</param>
        /// <param name="array">A reference to the array that is the buffer of mem. If null System.IO.MemoryStream.GetBuffer() is called.</param>
        /// <returns>The decoded value as a long.</returns>
        public static long readVarint(MemoryStream mem, byte[] array = null)
        {
            array = array ?? mem.GetBuffer();
            long value = 0;
            long i = mem.Position;
            long b = 0;
            b = array[i++]; value = (b & 0x7F); if ((b & 0x80) == 0x00) goto done;
            b = array[i++]; value |= (b & 0x7F) << 7; if ((b & 0x80) == 0x00) goto done;
            b = array[i++]; value |= (b & 0x7F) << 14; if ((b & 0x80) == 0x00) goto done;
            b = array[i++]; value |= (b & 0x7F) << 21; if ((b & 0x80) == 0x00) goto done;
            b = array[i++]; value |= (b & 0x7F) << 28; if ((b & 0x80) == 0x00) goto done;
            b = array[i++]; value |= (b & 0x7F) << 35; if ((b & 0x80) == 0x00) goto done;
            b = array[i++]; value |= (b & 0x7F) << 42; if ((b & 0x80) == 0x00) goto done;
            b = array[i++]; value |= (b & 0x7F) << 49; if ((b & 0x80) == 0x00) goto done;
            b = array[i++]; value |= (b & 0x7F) << 56; if ((b & 0x80) == 0x00) goto done;
            b = array[i++]; value |= (b & 0x7F) << 63; if ((b & 0x80) == 0x00) goto done;
        done:
            mem.Position = i;
            return value;
        }
        /// <summary>
        /// Deserialize a ProtocolBuffers message.
        /// </summary>
        /// <param name="memStream">The MemoryStream that contains the serialized ProtocolBuffers message.</param>
        /// <param name="message">An instance of the ProtocolBuffer message that should be deserialized.</param>
        /// <returns>The deserialized message object.</returns>
        public static IMessage Deserialize(MemoryStream memStream, IMessage message)
        {
            if (memStream == null || message == null) throw new Exception("Message object and inpud stream must be different from null");
            if (memStream.Length > 67108864) throw new Exception("ProBuff dose not support messages greater then 64MB. Remember, Googles recommended messagesize is 1MB or less.");
            byte[] memBuffer = memStream.GetBuffer();
            if (memStream.Length == 0 || (memStream.Length == 1 && memBuffer[0] == 0)) return message;
            int[] length = Serializer.getLength32(memBuffer, memStream.Position);
            int lengthWithBytes;
            if ((lengthWithBytes = length[0] + length[1] + (int)memStream.Position) > memStream.Length) throw new Exception(); //TODO: Error handleing
            memStream.Position += length[1];
            object tmp1;
            WireType wireType;
            int fieldNumber = 0, flag;
            bool isObject, isPacked;
            DataFormat dataFormat;
            int[] len;
            int lenWithBytes;
            TypeCode fieldTypeCode;
            Type fieldType,type;
            long n;
            ulong val;
            do
            {
                Serializer.getWireTypeAndFieldNumber(memStream, out wireType, out fieldNumber);
                flag = message.getFlags(fieldNumber);
                dataFormat = (DataFormat)(flag >> 5);
                isObject = Convert.ToBoolean((flag >> 4) & 0x1);
                isPacked = Convert.ToBoolean((flag >> 3) & 0x1);

                if (wireType != (WireType)(flag & 0x7))
                    throw new Exception(); //TODO: Error handleing

                if (isObject)
                {
                    try
                    {
                        if (!isPacked)
                        {
                            if ((type = message.getByTag(fieldNumber).GetType()).IsGenericType)
                                if ((type = type.GetGenericArguments()[0]).GetInterface(typeof(IMessage).FullName) == typeof(IMessage))
                                    tmp1 = Serializer.Deserialize(memStream, (IMessage)Activator.CreateInstance(type));
                                else
                                    tmp1 = decodeMessage(wireType, memStream, memBuffer, dataFormat, message, fieldNumber);
                            else
                                tmp1 = Serializer.Deserialize(memStream, (IMessage)message.getByTag(fieldNumber));
                            message.setByTag(fieldNumber, tmp1);
                            continue;

                        }
                        else
                        {
                            
                            switch (dataFormat)
                            {
                                case DataFormat.Default:
                                case DataFormat.TwosComplement:
                                    len = Serializer.getLength32(memBuffer, memStream.Position);
                                    lenWithBytes = len[1] + len[0] + (int)memStream.Position;
                                    memStream.Position += len[1];
                                    fieldTypeCode = Type.GetTypeCode(message.getByTag(fieldNumber).GetType().GetGenericArguments()[0]);
                                    switch (fieldTypeCode)
                                    {
                                        case TypeCode.Int32:
                                            do
                                            {
                                                message.setByTag(fieldNumber, (Int32)readVarint(memStream, memBuffer));
                                            }
                                            while (memStream.Position < lenWithBytes);
                                            break;
                                        case TypeCode.UInt32:
                                            do
                                            {
                                                message.setByTag(fieldNumber, (UInt32)readVarint(memStream, memBuffer));
                                            }
                                            while (memStream.Position < lenWithBytes);
                                            break;
                                        case TypeCode.Int64:
                                            do
                                            {
                                                message.setByTag(fieldNumber, (Int64)readVarint(memStream, memBuffer));
                                            }
                                            while (memStream.Position < lenWithBytes);
                                            break;
                                        case TypeCode.UInt64:
                                            do
                                            {
                                                message.setByTag(fieldNumber, (UInt64)readVarint(memStream, memBuffer));
                                            }
                                            while (memStream.Position < lenWithBytes);
                                            break;

                                        case TypeCode.Boolean:
                                            do
                                            {
                                                message.setByTag(fieldNumber, (Int16)readVarint(memStream, memBuffer));
                                            }
                                            while (memStream.Position < lenWithBytes);
                                            break;
                                        default: throw new Exception("Class to serialize is not Valid.");
                                    }
                                    continue;
                                case DataFormat.FixedSize:
                                    len = Serializer.getLength32(memBuffer, memStream.Position);
                                    lenWithBytes = len[1] + len[0] + (int)memStream.Position;
                                    memStream.Position += len[1];
                                    fieldType = message.getByTag(fieldNumber).GetType().GetGenericArguments()[0];
                                    switch (Type.GetTypeCode(fieldType))
                                    {
                                        case TypeCode.UInt32:
                                            do
                                            {
                                                message.setByTag(fieldNumber, BitConverter.ToUInt32(memBuffer, (int)memStream.Position));
                                                memStream.Position += 4;
                                            }
                                            while (memStream.Position < lenWithBytes);
                                            continue;
                                        case TypeCode.Int32:
                                            do
                                            {
                                                message.setByTag(fieldNumber, BitConverter.ToInt32(memBuffer, (int)memStream.Position));
                                                memStream.Position += 4;
                                            }
                                            while (memStream.Position < lenWithBytes);
                                            continue;
                                        case TypeCode.Single:
                                            do
                                            {
                                                message.setByTag(fieldNumber, BitConverter.ToSingle(memBuffer, (int)memStream.Position));
                                                memStream.Position += 4;
                                            }
                                            while (memStream.Position < lenWithBytes);
                                            continue;
                                        case TypeCode.UInt64:
                                            do
                                            {
                                                message.setByTag(fieldNumber, BitConverter.ToUInt64(memBuffer, (int)memStream.Position));
                                                memStream.Position += 8;
                                            }
                                            while (memStream.Position < lenWithBytes);
                                            continue;
                                        case TypeCode.Int64:
                                            do
                                            {
                                                message.setByTag(fieldNumber, BitConverter.ToInt64(memBuffer, (int)memStream.Position));
                                                memStream.Position += 8;
                                            }
                                            while (memStream.Position < lenWithBytes);
                                            continue;
                                        case TypeCode.Double:
                                            do
                                            {
                                                message.setByTag(fieldNumber, BitConverter.ToDouble(memBuffer, (int)memStream.Position));
                                                memStream.Position += 8;
                                            }
                                            while (memStream.Position < lenWithBytes);
                                            continue;
                                        default: throw new Exception("Class to serialize is not Valid.");
                                    }
                                case DataFormat.ZigZag:
                                    len = Serializer.getLength32(memBuffer, memStream.Position);
                                    lenWithBytes = len[1] + len[0] + (int)memStream.Position;
                                    memStream.Position += len[1];
                                    do
                                    {
                                        n = readVarint(memStream, memBuffer);
                                        val = (ulong)n;
                                        switch (val & 0x1)
                                        {
                                            case 0:
                                                message.setByTag(fieldNumber, val >> 1);
                                                continue;
                                            default:
                                                message.setByTag(fieldNumber,(ulong)(-1 * ((long)(val >> 1) + 1)));
                                                continue;
                                        }
                                        //message.setByTag(fieldNumber, (ulong)n);
                                    }
                                    while (memStream.Position < lenWithBytes);
                                    continue;
                                default: throw new Exception(); //TODO: Error handling
                            }
                        }
                    }
                    catch (Exception e)
                    {
                        System.Console.WriteLine(e.Message);
                        Console.WriteLine(e.StackTrace);
                        continue;
                    }

                }
                message.setByTag(fieldNumber, decodeMessage(wireType, memStream, memBuffer, dataFormat, message, fieldNumber));

            }
            while (memStream.Position < lengthWithBytes);
            if (message.checkRequirements())
                return message;
            else throw new Exception("Not all requirements are met.");
        }
        private static void getWireTypeAndFieldNumber(MemoryStream memStream, out WireType wireType, out int fieldNumber)
        {
            uint wireTypeAndField = (uint)readVarint(memStream, memStream.GetBuffer());
            short wire = (short)(wireTypeAndField & 0x0007);
            if (wire == 4 || wire == 3) throw new Exception("Groups are not supported.");
            wireType = (WireType)(wireTypeAndField & 0x0007);
            fieldNumber = (int)(wireTypeAndField >> 3);
        }
        private static WireType createWireType(Type type, DataFormat dataFormat)
        {
            switch (Type.GetTypeCode(type))
            {
                case TypeCode.Int32:
                case TypeCode.UInt32: return (dataFormat == DataFormat.FixedSize) ? WireType.Fixed32 : WireType.Varint;
                case TypeCode.Int64:
                case TypeCode.UInt64: return (dataFormat == DataFormat.FixedSize) ? WireType.Fixed64 : WireType.Varint;
                case TypeCode.Int16:
                case TypeCode.Boolean: return WireType.Varint;
                case TypeCode.String:
                case TypeCode.Byte: return WireType.String;
                case TypeCode.Object: return (type.GetInterface(typeof(IMessage).FullName) == typeof(IMessage)) ? WireType.String : WireType.Varint;
                case TypeCode.Single: return WireType.Fixed32;
                case TypeCode.Double: return WireType.Fixed64;
                default: break; //throw new Exception();
            }
            switch (dataFormat)
            {
                case DataFormat.FixedSize: return Type.GetTypeCode(type) == TypeCode.Single ? WireType.Fixed32 : WireType.Fixed64;
                case DataFormat.TwosComplement:
                case DataFormat.ZigZag:
                case DataFormat.Default: return WireType.Varint;
                default: throw new InvalidOperationException();
            }
        }
        private static object decodeMessage(WireType wireType, MemoryStream memStream, byte[] memBuffer, DataFormat dataFormat, IMessage message, int fieldNumber)
        {
            object result;
            long n;
            switch (wireType)
            {
                case WireType.Varint:
                    n = readVarint(memStream, memBuffer);
                    ulong val = (ulong)n;
                    switch (dataFormat)
                    {
                        case DataFormat.ZigZag:
                            switch (val & 0x1)
                            {
                                case 0:
                                    return val >> 1;
                                default:
                                    return (ulong)(-1 * ((long)(val >> 1) + 1));
                            }
                        default:
                            //if (!message.getByTag(fieldNumber).GetType().ToString().Contains("UInt"))
                            //    return n;
                            //else
                                return val;
                    }
                case WireType.Fixed64:
                    result = BitConverter.ToUInt64(memBuffer, (int)memStream.Position);
                    memStream.Position += 8;
                    return result;
                case WireType.String:
                    int[] len = getLength32(memBuffer, memStream.Position);
                    memStream.Position += len[1];
                    result = ASCIIEncoding.UTF8.GetString(memBuffer, (int)memStream.Position, len[0]);
                    memStream.Position += len[0];
                    return result;
                case WireType.Fixed32:
                    result = BitConverter.ToUInt32(memBuffer, (int)memStream.Position);
                    memStream.Position += 4;
                    return result;
                default: throw new Exception(); //TODO: Error handleing
            }
        }

        private static int[] _bitcounts = InitializeBitcounts();
        private static int[] InitializeBitcounts()
        {
            int[] table = new int[65536];
            int position1 = -1;
            int position2 = -1;
            for (int i = 1; i < 65536; i++, position1++)
            {
                if (position1 == position2)
                {
                    position1 = 0;
                    position2 = i;
                }
                table[i] = table[position1] + 1;
            }
            return table;
        }
        private static int PrecomputedBitcount(ulong value)
        {
            return _bitcounts[value & 65535] + _bitcounts[(value >> 16) & 65535] + _bitcounts[(value >> 32) & 65535] + _bitcounts[(value >> 48) & 65535];
        }
        /// <summary>
        /// Serializes a given message to a System.IO.MemoryStream.
        /// </summary>
        /// <param name="memStream">The System.IO.MemoryStream where the serialized data goes.</param>
        /// <param name="message">The message to serialize.</param>
        public static void Serialize(MemoryStream memStream, IMessage message)
        {
            if (!message.checkRequirements())
                throw new Exception("Not all requirements are met.");
            byte[] memBuffer = memStream.GetBuffer();
            ulong[] available = message.getExistingTags();
            if (available == null) return;
            ulong count = 0;
            for (int i = 0; i < available.Length; i++)
            {
                count += (ulong)PrecomputedBitcount(available[i]);
            }
            int[] tags = new int[count];
            int k = 0;
            for (int i = 0; i < available.Length; i++)
            {
                for (int j = 0; j < 64; j++)
                {
                    if (k >= tags.Length) break;
                    if (((ulong)(available[i] & ((ulong)1 << j))) != 0) { tags[k] = j + 1 + (i << 6); k++; }
                }
            }
            long mem_pos;
            object value;
            Type type;
            int flag, tag;
            DataFormat dataFormat;
            bool isObject;
            bool isPacked;
            WireType wireType;
            MemoryStream currentObjectStream = new MemoryStream();
            for (int i = 0; i < tags.Length; i++)
            {
                tag = tags[i];
                value = message.getByTag(tag);
                type = value.GetType();
                flag = message.getFlags(tag);
                dataFormat = (DataFormat)(flag >> 5);
                isObject = Convert.ToBoolean((flag >> 4) & 0x1);
                isPacked = Convert.ToBoolean((flag >> 3) & 0x1);
                wireType = Serializer.createWireType(type, dataFormat);
                byte[] wireTypeAndTag;
                writeVarint((ulong)(tag << 3 | (int)wireType), out wireTypeAndTag);
                if (!value.GetType().IsGenericType) currentObjectStream.Write(wireTypeAndTag, 0, wireTypeAndTag.Length);
                IList list;
                if (isObject)
                {
                    if (!isPacked)
                    {
                        if (!value.GetType().IsGenericType)
                        {
                            MemoryStream tempStream = new MemoryStream(4096);
                            Serialize(tempStream, (IMessage)value);
                            tempStream.WriteTo(currentObjectStream);
                        }
                        else
                        {
                            list = (IList)value;
                            if (list.Count <= 0) continue;
                            wireType = Serializer.createWireType(type = list[0].GetType()/*.GetGenericArguments()[0]*/, dataFormat);
                            writeVarint((ulong)(tag << 3 | (int)wireType), out wireTypeAndTag);
                            //if (list.Count <= 0) continue;
                            if (type.GetInterface(typeof(IMessage).FullName) == typeof(IMessage))
                                for (int p = 0; p < list.Count; p++)
                                {
                                    currentObjectStream.Write(wireTypeAndTag, 0, wireTypeAndTag.Length);
                                    Serialize(currentObjectStream, (IMessage)list[p]);
                                }
                            else
                                for (int p = 0; p < list.Count; p++)
                                {
                                    currentObjectStream.Write(wireTypeAndTag, 0, wireTypeAndTag.Length);
                                    encodeMessage(wireType, currentObjectStream, list[p], dataFormat);
                                }
                        }
                        continue;

                    }
                    else
                    {
                        wireType = WireType.String;
                        writeVarint((ulong)(tag << 3 | (int)wireType), out wireTypeAndTag);
                        currentObjectStream.Write(wireTypeAndTag, 0, wireTypeAndTag.Length);
                        long oldLength = 0, pos;
                        switch (dataFormat)
                        {
                            case DataFormat.Default:
                            case DataFormat.TwosComplement:
                                list = (IList)value;
                                if (list.Count <= 0) continue;
                                byte[] toWrite;
                                int toWritePos = 0;
                                oldLength = (list.Count * 3 > Int32.MaxValue) ? Int32.MaxValue : list.Count * 3;
                                MemoryStream mStream = new MemoryStream((int)oldLength);
                                toWrite = new byte[oldLength];
                                for (int p = 0; p < list.Count; p++)
                                {
                                    if (toWritePos >= oldLength - 8) Array.Copy(toWrite, (toWrite = new byte[(oldLength = oldLength + 1024)]), toWritePos);
                                    writeRawVarint(Convert.ToUInt64(list[p]), toWrite, ref toWritePos);
                                }
                                writeVarint(toWritePos, currentObjectStream);
                                currentObjectStream.Write(toWrite, 0, toWritePos);
                                continue;
                            case DataFormat.FixedSize:
                                list = (IList)value;
                                if (list.Count <= 0) continue;
                                Type listType;
                                switch (createWireType((listType = value.GetType().GetGenericArguments()[0]), dataFormat))
                                {
                                    case WireType.Fixed64:
                                        writeVarint(list.Count * 8, currentObjectStream);
                                        switch (Type.GetTypeCode(listType))
                                        {
                                            case TypeCode.UInt64:
                                                for (int p = 0; p < list.Count; p++)
                                                {
                                                    byte[] o = BitConverter.GetBytes((ulong)(list[p]));
                                                    currentObjectStream.Write(o, 0, 8);
                                                }
                                                break;
                                            case TypeCode.Double:
                                                for (int p = 0; p < list.Count; p++)
                                                {
                                                    byte[] o = BitConverter.GetBytes((double)(list[p]));
                                                    currentObjectStream.Write(o, 0, 8);
                                                }
                                                break;
                                            default:
                                                for (int p = 0; p < list.Count; p++)
                                                {
                                                    byte[] o = BitConverter.GetBytes((long)(list[p]));
                                                    currentObjectStream.Write(o, 0, 8);
                                                }
                                                break;
                                        }
                                        break;

                                    case WireType.Fixed32:

                                        writeVarint(list.Count * 4, currentObjectStream);
                                        switch (Type.GetTypeCode(listType))
                                        {
                                            case TypeCode.Int32:
                                                for (int p = 0; p < list.Count; p++)
                                                {
                                                    byte[] o = BitConverter.GetBytes((int)(list[p]));
                                                    currentObjectStream.Write(o, 0, 4);
                                                }
                                                break;

                                            case TypeCode.Single:
                                                for (int p = 0; p < list.Count; p++)
                                                {
                                                    byte[] o = BitConverter.GetBytes((float)(list[p]));
                                                    currentObjectStream.Write(o, 0, 4);
                                                }
                                                break;


                                            default:
                                                for (int p = 0; p < list.Count; p++)
                                                {
                                                    byte[] o = BitConverter.GetBytes((uint)(list[p]));
                                                    currentObjectStream.Write(o, 0, 4);
                                                }
                                                break;
                                        }
                                        break;
                                    default: break;
                                }
                                continue;
                            case DataFormat.ZigZag:
                                list = (IList)value;
                                if (list.Count <= 0) continue;
                                MemoryStream m_Stream = new MemoryStream((int)oldLength);
                                ulong n;
                                int nn;
                                switch (Type.GetTypeCode(value.GetType().GetGenericArguments()[0]))
                                {

                                    case TypeCode.Int32:
                                        {
                                            m_Stream = new MemoryStream(list.Count * 3);
                                            for (int p = 0; p < list.Count; p++)
                                            {
                                                if (m_Stream.Position >= oldLength + 1024) m_Stream.SetLength((oldLength = m_Stream.Position) + 1024);
                                                nn = (int)(list[p]);
                                                writeVarint((nn << 1) ^ (nn >> 31), m_Stream);
                                            }
                                            writeVarint(m_Stream.Position, currentObjectStream);
                                            pos = m_Stream.Position + currentObjectStream.Position;
                                            m_Stream.WriteTo(currentObjectStream);
                                            currentObjectStream.Position = pos;
                                        }
                                        break;
                                    default:
                                        {
                                            m_Stream = new MemoryStream(list.Count * 6);
                                            for (int p = 0; p < list.Count; p++)
                                            {
                                                if (m_Stream.Position >= oldLength + 1024) m_Stream.SetLength((oldLength = m_Stream.Position) + 1024);
                                                n = (ulong)(long)(list[p]);
                                                writeVarint((n << 1) ^ (n >> 63), m_Stream);
                                            }
                                            writeVarint(m_Stream.Position, currentObjectStream);
                                            pos = m_Stream.Position + currentObjectStream.Position;
                                            m_Stream.WriteTo(currentObjectStream);
                                            currentObjectStream.Position = pos;
                                        }
                                        break;
                                }

                                continue;
                            default: throw new Exception(); //TODO: Error handling
                        }
                    }
                }
                else encodeMessage(wireType, currentObjectStream, value, dataFormat);
            }
            writeVarint(currentObjectStream.Position, memStream);
            mem_pos = currentObjectStream.Position + memStream.Position;
            currentObjectStream.WriteTo(memStream);
            memStream.Position = mem_pos;
            if (memStream.Position > 67108864) throw new Exception("ProBuff dose not support messages greater then 64MB. Remember, Googles recommended messagesize is 1MB or less.");
        }
        private static void encodeMessage(WireType wireType, MemoryStream memStream, object value, DataFormat dataFormat)
        {
            ulong n;
            switch (wireType)
            {
                case WireType.Varint:
                    n = (ulong)(value);
                    if (DataFormat.ZigZag == dataFormat) n = (TypeCode.Int64 == Type.GetTypeCode(value.GetType())) ? (n << 1) ^ (n >> 63) : (n << 1) ^ (n >> 31);
                    writeVarint(n, memStream);
                    return;
                case WireType.Fixed64:
                    switch (Type.GetTypeCode(value.GetType()))
                    {
                        case TypeCode.Double:
                            memStream.Write(BitConverter.GetBytes((double)(value)), 0, 8);
                            return;
                        case TypeCode.Int64:
                            memStream.Write(BitConverter.GetBytes((long)(value)), 0, 8);
                            return;
                        case TypeCode.UInt64:
                            memStream.Write(BitConverter.GetBytes((ulong)(value)), 0, 8);
                            return;
                        default:
                            throw new Exception("This should not happen. Please report this issue.");
                    }
                case WireType.String:
                    String str = (String)value;
                    writeVarint((str.Length), memStream);
                    byte[] stringBytes = new byte[str.Length];
                    for (int p = 0; p < stringBytes.Length; p++)
                    {
                        stringBytes[p] = Convert.ToByte(str[p]);
                    }
                    memStream.Write(stringBytes, 0, stringBytes.Length);
                    return;
                case WireType.Fixed32:
                    switch (Type.GetTypeCode(value.GetType()))
                    {
                        case TypeCode.Single:
                            memStream.Write(BitConverter.GetBytes((float)(value)), 0, 4);
                            return;
                        case TypeCode.Int32:
                            memStream.Write(BitConverter.GetBytes((int)(value)), 0, 4);
                            return;
                        case TypeCode.UInt32:
                            memStream.Write(BitConverter.GetBytes((uint)(value)), 0, 4);
                            return;
                        default:
                            throw new Exception("This should not happen. Please report this issue.");
                    }
                default: throw new Exception(); //TODO: Error handleing
            }
        }
        private static void writeRawVarint(ulong i, byte[] array, ref int pos)
        {
            array[pos++] = (byte)(((i >> 7) == 0) ? (i) : (i | 0x80)); if (((i >> 7) == 0)) goto done;
            array[pos++] = (byte)(((i >> 14) == 0) ? (i >> 7) : (i >> 7 | 0x80)); if (((i >> 14) == 0)) goto done;
            array[pos++] = (byte)(((i >> 21) == 0) ? (i >> 14) : (i >> 14 | 0x80)); if (((i >> 21) == 0)) goto done;
            array[pos++] = (byte)(((i >> 28) == 0) ? (i >> 21) : (i >> 21 | 0x80)); if (((i >> 28) == 0)) goto done;
            array[pos++] = (byte)(((i >> 35) == 0) ? (i >> 28) : (i >> 28 | 0x80)); if (((i >> 35) == 0)) goto done;
            array[pos++] = (byte)(((i >> 42) == 0) ? (i >> 35) : (i >> 35 | 0x80)); if (((i >> 42) == 0)) goto done;
            array[pos++] = (byte)(((i >> 49) == 0) ? (i >> 42) : (i >> 42 | 0x80)); if (((i >> 49) == 0)) goto done;
            array[pos++] = (byte)(((i >> 56) == 0) ? (i >> 49) : (i >> 49 | 0x80)); if (((i >> 56) == 0)) goto done;
            array[pos++] = (byte)(((i >> 63) == 0) ? (i >> 56) : (i >> 56 | 0x80)); if (((i >> 63) == 0)) goto done;
            array[pos++] = (byte)(i >> 63);
        done:
            return;
        }
        private static void writeVarint(ulong value, byte[] array, ref int pos)
        {
            while (true)
            {
                switch ((value >> 7))
                {
                    case 0:
                        array[pos++] = (byte)value;
                        return;
                    default:
                        array[pos++] = (byte)((value & 0x7F) | 0x80);
                        value >>= 7;
                        continue;
                }
            }
        }
        /// <summary>
        /// Writes an ulong varint encoded to an byte array.
        /// </summary>
        /// <param name="value">The ulong to write.</param>
        /// <param name="array">The array to write the value to.</param>
        public static void writeVarint(ulong value, out byte[] array)
        {
            int index = 0;
            result[index++] = (byte)(((value >> 7) == 0) ? (value) : (value | 0x80)); if (((value >> 7) == 0)) goto done;
            result[index++] = (byte)(((value >> 14) == 0) ? (value >> 7) : (value >> 7 | 0x80)); if (((value >> 14) == 0)) goto done;
            result[index++] = (byte)(((value >> 21) == 0) ? (value >> 14) : (value >> 14 | 0x80)); if (((value >> 21) == 0)) goto done;
            result[index++] = (byte)(((value >> 28) == 0) ? (value >> 21) : (value >> 21 | 0x80)); if (((value >> 28) == 0)) goto done;
            result[index++] = (byte)(((value >> 35) == 0) ? (value >> 28) : (value >> 28 | 0x80)); if (((value >> 35) == 0)) goto done;
            result[index++] = (byte)(((value >> 42) == 0) ? (value >> 35) : (value >> 35 | 0x80)); if (((value >> 42) == 0)) goto done;
            result[index++] = (byte)(((value >> 49) == 0) ? (value >> 42) : (value >> 42 | 0x80)); if (((value >> 49) == 0)) goto done;
            result[index++] = (byte)(((value >> 56) == 0) ? (value >> 49) : (value >> 49 | 0x80)); if (((value >> 56) == 0)) goto done;
            result[index++] = (byte)(((value >> 63) == 0) ? (value >> 56) : (value >> 56 | 0x80)); if (((value >> 63) == 0)) goto done;
            result[index++] = (byte)(value >> 63);
        done:
            array = new byte[index];
            Array.Copy(result, array, index);
        }
        private static void writeVarint(ulong value, MemoryStream memStream)
        {
            int pos = 0;
            //result[index++] = (byte)(((i >> 7) == 0) ? (i) : (i | 0x80)); if (((i >> 7) == 0)) goto done;
            //result[index++] = (byte)(((i >> 14) == 0) ? (i >> 7) : (i >> 7 | 0x80)); if (((i >> 14) == 0)) goto done;
            //result[index++] = (byte)(((i >> 21) == 0) ? (i >> 14) : (i >> 14 | 0x80)); if (((i >> 21) == 0)) goto done;
            //result[index++] = (byte)(((i >> 28) == 0) ? (i >> 21) : (i >> 21 | 0x80)); if (((i >> 28) == 0)) goto done;
            //result[index++] = (byte)(((i >> 35) == 0) ? (i >> 28) : (i >> 28 | 0x80)); if (((i >> 35) == 0)) goto done;
            //result[index++] = (byte)(((i >> 42) == 0) ? (i >> 35) : (i >> 35 | 0x80)); if (((i >> 42) == 0)) goto done;
            //result[index++] = (byte)(((i >> 49) == 0) ? (i >> 42) : (i >> 42 | 0x80)); if (((i >> 49) == 0)) goto done;
            //result[index++] = (byte)(((i >> 56) == 0) ? (i >> 49) : (i >> 49 | 0x80)); if (((i >> 56) == 0)) goto done;
            //result[index++] = (byte)(((i >> 63) == 0) ? (i >> 56) : (i >> 56 | 0x80)); if (((i >> 63) == 0)) goto done;
            //result[index++] = (byte)(i >> 63);
            while (true)
            {
                switch ((value >> 7))
                {
                    case 0:
                        result[pos++] = (byte)value;
                        memStream.Write(result, 0, pos);
                        return;
                    default:
                        result[pos++] = (byte)((value & 0x7F) | 0x80);
                        value >>= 7;
                        continue;
                }
            }
        //done:
        //  memStream.Write(result, 0, pos);
        }
        private static void writeVarint(long value, MemoryStream memStream)
        {
            int pos = 0;
            while (true)
            {
                switch ((value >> 7))
                {
                    case 0:
                        result[pos++] = (byte)value;
                        memStream.Write(result, 0, pos);
                        return;
                    default:
                        result[pos++] = (byte)((value & 0x7F) | 0x80);
                        value >>= 7;
                        continue;
                }
            }
        }
        private static void writeVarint(int value, MemoryStream memStream)
        {
            int pos = 0;
            while (true)
            {
                switch ((value >> 7))
                {
                    case 0:
                        result[pos++] = (byte)value;
                        memStream.Write(result, 0, pos);
                        return;
                    default:
                        result[pos++] = (byte)((value & 0x7F) | 0x80);
                        value >>= 7;
                        continue;
                }
            }
        }
        private static void writeVarint(uint value, MemoryStream memStream)
        {
            int pos = 0;
            while (true)
            {
                switch ((value >> 7))
                {
                    case 0:
                        result[pos++] = (byte)value;
                        memStream.Write(result, 0, pos);
                        return;
                    default:
                        result[pos++] = (byte)((value & 0x7F) | 0x80);
                        value >>= 7;
                        continue;
                }
            }
        }
    }

    enum DataFormat
    {
        Default = 0,
        ZigZag = 1,
        TwosComplement = 2,
        FixedSize = 3,
    }
    enum WireType
    {
        Varint = 0,
        Fixed64 = 1,
        String = 2,
        Fixed32 = 5,
    }
}
